﻿//-----------------------------------------------------------------------
// <copyright file="Repository.cs" company="deixei.com">
//     Copyright (c) deixei.com. All rights reserved.
// </copyright>
// <author>Marcio Parente</author>
//----------------------------------------------------------------------
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]

namespace Deixei.Domain
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Entity;
    using System.Data.Objects;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using Microsoft.Practices.Unity;
    using Microsoft.Practices.Unity.InterceptionExtension;
    using System.Threading;
    using Deixei.Infrastructure;

    /// <summary>
    /// Repository of Model Entities
    /// </summary>
    /// <typeparam name="T">Model Entities</typeparam>
    public class Repository<T> : IRepository<T> where T : class, new()
    {

        private readonly Expression<Func<T, bool>> _notArchivedPredicate;


        public Repository(IUnitOfWork unitOfWork)
            : base()
        {
            CommonUtil.CheckForNull(unitOfWork, "UnitOfWork canot be null.");
            if ( typeof(IArchivable).IsAssignableFrom(typeof(T)) )
            {
                _notArchivedPredicate = BuildNotArchivedPredicate();
            }

            this.EnrolInUnitOfWork(unitOfWork);
        }


        private Expression<Func<T, bool>> BuildNotArchivedPredicate()
        {
            ParameterExpression entityType = Expression.Parameter(typeof(T), "ent");
            Expression entIsNotDeleted = Expression.Equal(Expression.Property(entityType, "Deleted"), Expression.Constant(null));

            return Expression.Lambda<Func<T, bool>>(entIsNotDeleted, entityType);
        }

        private Expression<Func<T, bool>> ComposeWithNotArchivedPredicate(Expression<Func<T, bool>> where)
        {
            Expression<Func<T, bool>> composedExpression = where.And(_notArchivedPredicate);

            return composedExpression;
        }

        /// <summary>
        /// object set
        /// </summary>
        private IDbSet<T> objectset;

        /// <summary>
        /// Gets the object set.
        /// </summary>
        /// <value>
        /// The object set.
        /// </value>
        public IDbSet<T> ObjectSet
        {
            get 
            {
                if (this.objectset == null)
                {
                    this.objectset = this.UnitOfWork.Context.Set<T>();
                }

                return this.objectset;
            }
        }

        /// <summary>
        /// Ases the queryable.
        /// </summary>
        /// <returns></returns>
        public IQueryable<T> AsQueryable()
        {
            return typeof(IArchivable).IsAssignableFrom(typeof(T)) ? ObjectSet.Where(_notArchivedPredicate) : ObjectSet.AsQueryable();
        }

        /// <summary>
        /// Gets or sets the unit of work.
        /// </summary>
        /// <value>
        /// The unit of work.
        /// </value>
        /// 
        [Dependency]
        public IUnitOfWork UnitOfWork { get; set; }

        /// <summary>
        /// Enrols the in unit of work.
        /// </summary>
        /// <param name="unitOfWork">The unit of work.</param>
        public void EnrolInUnitOfWork(IUnitOfWork unitOfWork)
        {
            this.UnitOfWork = unitOfWork;
            this.UnitOfWork.Context.Configuration.LazyLoadingEnabled = true;
            this.UnitOfWork.Context.Configuration.ProxyCreationEnabled = false;
        }

        /// <summary>
        /// Gets all.
        /// </summary>
        /// <param name="includeProperties">The include properties.</param>
        /// <returns></returns>
        public IQueryable<T> GetAll(params Expression<Func<T, object>>[] includeProperties)
        {

            if ( typeof(IArchivable).IsAssignableFrom(typeof(T)) )
            {
                return this.Find(_notArchivedPredicate, includeProperties);
            }

            IQueryable<T> query = AsQueryable();
            return PerformInclusions(includeProperties, query);
        }

        /// <summary>
        /// Finds the specified where.
        /// </summary>
        /// <param name="where">The where.</param>
        /// <param name="includeProperties">The include properties.</param>
        /// <returns></returns>
        public IQueryable<T> Find(Expression<Func<T, bool>> where,
                           params Expression<Func<T, object>>[] includeProperties)
        {
            if ( typeof(IArchivable).IsAssignableFrom(typeof(T)) )
            {
                where = ComposeWithNotArchivedPredicate(where);
            }

            IQueryable<T> query = AsQueryable();
            query = PerformInclusions(includeProperties, query);
            return query.Where(where);
        }

        /// <summary>
        /// Singles the specified where.
        /// </summary>
        /// <param name="where">The where.</param>
        /// <param name="includeProperties">The include properties.</param>
        /// <returns></returns>
        public T Single(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] includeProperties)
        {
            if ( typeof(IArchivable).IsAssignableFrom(typeof(T)) )
            {
                where = ComposeWithNotArchivedPredicate(where);
            }

            IQueryable<T> query = AsQueryable();
            query = PerformInclusions(includeProperties, query);
            return query.SingleOrDefault(where);
        }

        /// <summary>
        /// Firsts the specified where.
        /// </summary>
        /// <param name="where">The where.</param>
        /// <param name="includeProperties">The include properties.</param>
        /// <returns></returns>
        public T First(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] includeProperties)
        {
            if ( typeof(IArchivable).IsAssignableFrom(typeof(T)) )
            {
                where = ComposeWithNotArchivedPredicate(where);
            }

            IQueryable<T> query = AsQueryable();
            query = PerformInclusions(includeProperties, query);
            return query.FirstOrDefault(where);
        }

        /// <summary>
        /// Adds the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Add(T entity)
        {
            //IDomainEntity, IArchivable, IAuditable
            IAuditable auditable = entity as IAuditable;

            if (auditable != null)
            {
                auditable.CreatedBy = Thread.CurrentPrincipal.Identity.Name;
                auditable.Created = DateTime.Now;
            }

            IArchivable archivable = entity as IArchivable;
            if ( archivable != null )
            {
                archivable.DeletedBy = "";
                archivable.Deleted = null;
            }

            this.ObjectSet.Add(entity);
        }

        /// <summary>
        /// Deletes the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="forceDelete">if set to <c>true</c> [force delete].</param>
        public void Delete(T entity, bool forceDelete = false)
        {
            IArchivable archivable = entity as IArchivable;

            if (forceDelete)
            {
                this.ObjectSet.Remove(entity);
            }
            else
            { 
                if (archivable != null)
                {
                    archivable.DeletedBy = Thread.CurrentPrincipal.Identity.Name;
                    archivable.Deleted = DateTime.Now;
                    this.Update(entity);
                }
            }
        }

        /// <summary>
        /// Updates the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Update(T entity)
        {
            this.UnitOfWork.Context.ChangeTracker.DetectChanges();
            IAuditable auditable = entity as IAuditable;
            if (auditable != null)
            {
                auditable.UpdatedBy = Thread.CurrentPrincipal.Identity.Name;
                auditable.Updated = DateTime.Now;
            }
            var entry = this.UnitOfWork.Context.Entry(entity);

            if ( entry.State == EntityState.Detached )
            {
                this.ObjectSet.Attach(entity);
            }
            entry.State = EntityState.Modified;
        }

        public int Count
        {
            get
            {
                return this.ObjectSet.Count();
            }
        }

        private static IQueryable<T> PerformInclusions(IEnumerable<Expression<Func<T, object>>> includeProperties,
                                               IQueryable<T> query)
        {
            return includeProperties.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
        }

    }
}
